/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package maxbe.rel2xml.generator.docs.constraints;

import java.util.ArrayList;
import java.util.List;

import maxbe.rel2xml.generator.docs.DataType;
import maxbe.rel2xml.util.Random;

/**
 * Generates random-length concatenations of a fixed set of <tt>syllables</tt>
 * with an optional fixed part. Not specifying a length range will result in
 * strings of length <tt>syllables.length</tt>. Not requesting random
 * concatentations will result in iterating through all syllable combinations of
 * length <tt>min</tt>.</br> With both random and (start and/or end) constant
 * parts specified, the start part will always precede the random part as will
 * the random part precede the end part.</br> Note the possibly undesired
 * semantics of having a fixed random part (can split syllables).</br>
 * 
 * Represents the value constructor "concat[x..y] of (&ltlist_of_syllables&gt)"
 * 
 * @author Max Bechtold
 * 
 */
public class RandomStringConcats implements ValueConstructor {

	private final int minCount;
	private final int maxCount;

	/**
	 * -1 means not random, 0 means uniformly random, random > 0 means nuRandom
	 * with parameter a = random
	 */
	private final int random;

	/** Parameter for nuRandom method */
	private int c;

	private final String[] syllables;
	private final String fixStart;
	private final String fixRandom;
	private final String fixEnd;

	private int[] indexes;

	private final java.util.Random rand1;
	private final java.util.Random rand2;
	private final java.util.Random rand3;

	private static long seed1 = 455286454;
	private static long seed2 = 928746100;
	private static long seed3 = 893467828;

	private static final List<DataType> types;

	static {
		types = new ArrayList<DataType>();
		types.add(DataType.VARCHAR);
		types.add(DataType.CHAR);
	}

	public RandomStringConcats(String[] syllables) {
		this(-1, syllables.length, syllables.length, syllables, null, null,
				null);
	}

	public RandomStringConcats(int minCount, int maxCount, String[] syllables) {
		this(-1, minCount, maxCount, syllables, null, null, null);
	}

	public RandomStringConcats(int random, int minCount, int maxCount,
			String[] syllables, String fixStart, String fixRandom, String fixEnd) {
		this.rand1 = new java.util.Random(seed1++);
		this.rand2 = new java.util.Random(seed2++);
		this.rand3 = new java.util.Random(seed3++);

		if (random < -1)
			random = -1;
		else if (random > 0)
			c = rand2.nextInt(random + 1);

		if (minCount > maxCount || random == -1)
			maxCount = minCount;

		this.minCount = minCount;
		this.maxCount = maxCount;
		this.syllables = syllables;

		this.random = random;
		if (random == -1) {
			indexes = new int[minCount];
			indexes[minCount - 1] = -1;
		}

		this.fixStart = fixStart;
		this.fixRandom = fixRandom;
		this.fixEnd = fixEnd;
	}

	@Override
	public boolean lengthVaries() {
		boolean lengthVaries = (minCount == maxCount);
		if (!lengthVaries)
			return false;

		int syllableLength = syllables[0].length();
		for (String syllable : syllables) {
			if (syllable.length() != syllableLength) {
				lengthVaries = false;
				break;
			}
		}

		return lengthVaries;
	}

	@Override
	public int getLength() {
		int maxSyllableLength = 0;
		for (String syllable : syllables) {
			if (syllable.length() > maxSyllableLength) {
				maxSyllableLength = syllable.length();
			}
		}
		return maxCount * maxSyllableLength;
	}

	@Override
	public int getSubLength() {
		return -1;
	}

	@Override
	public boolean needsAnnounce() {
		return false;
	}

	@Override
	public void announce(int number) {
		// No preparation needed
	}

	@Override
	public void mark(int number) {
		// Ignore, always global
	}

	@Override
	public String next() {
		String result = "";
		if (random == 0) {
			int count = (minCount == maxCount) ? minCount : rand1
					.nextInt(maxCount - minCount + 1) + minCount;
			for (int i = 0; i < count; i++) {
				int index = rand2.nextInt(syllables.length);
				result += syllables[index];
			}
		} else if (random > 0) {
			// Compute non-uniformly distributed random values (as demanded by
			// TPC-C for certain attribute values)
			int count = (minCount == maxCount) ? minCount : rand1
					.nextInt(maxCount - minCount + 1) + minCount;
			int cipher = Random.nuRandom(random, c, 0,
					(int) (Math.pow(10, count) - 1), rand2);
			for (char c : String.format("%0" + count + "d", cipher)
					.toCharArray()) {
				int index = Integer.parseInt(Character.toString(c))
						% syllables.length;
				result += syllables[index];
			}
		} else {
			// Switch to next combination
			nextCombination();
			for (int i = 0; i < indexes.length; i++)
				result += syllables[indexes[i]];
		}
		return attachFixParts(result);
	}

	private void nextCombination() {
		for (int i = indexes.length - 1; i >= 0; i--)
			if (++indexes[i] == syllables.length)
				indexes[i] = 0;
			else
				break;
	}

	@Override
	public List<String> next(int number) {
		List<String> result = new ArrayList<String>();

		if (random == 0)
			for (int k = 0; k < number; k++) {
				String tmp = "";
				int count = (minCount == maxCount) ? minCount : rand1
						.nextInt(maxCount - minCount + 1) + minCount;
				for (int i = 0; i < count; i++) {
					int index = rand2.nextInt(syllables.length);
					tmp += syllables[index];
				}

				result.add(attachFixParts(tmp));
			}
		else if (random > 0)
			for (int k = 0; k < number; k++) {
				// Compute non-uniformly distributed random values (as demanded
				// by TPC-C for certain attribute values
				String tmp = "";
				int count = (minCount == maxCount) ? minCount : rand1
						.nextInt(maxCount - minCount + 1) + minCount;
				int cipher = Random.nuRandom(random, c, 0,
						(int) (Math.pow(10, count) - 1), rand2);
				for (char c : String.format("%0" + count + "d", cipher)
						.toCharArray()) {
					int index = Integer.parseInt(Character.toString(c))
							% syllables.length;
					tmp += syllables[index];
				}
				result.add(attachFixParts(tmp));
			}
		else
			for (int k = 0; k < number; k++) {
				String next = "";
				nextCombination();
				for (int i = 0; i < indexes.length; i++)
					next += syllables[indexes[i]];
				result.add(attachFixParts(next));
			}

		return result;
	}

	private String attachFixParts(String base) {
		if (fixRandom != null) {
			int index = rand3.nextInt(base.length());
			if (index == 0) {
				base = fixRandom + base;
			} else if (index == base.length() - 1) {
				base = base + fixRandom;
			} else {
				base = base.substring(0, index) + fixRandom
						+ base.substring(index + 1);
			}
		}

		if (fixStart != null) {
			base = fixStart + base;
		}
		if (fixEnd != null) {
			base += fixEnd;
		}
		return base;
	}

	@Override
	public String toString() {
		String result = "";
		if (random == 0)
			result = "random ";
		else if (random > 0)
			result = "nuRandom(" + random + ") ";
		result += String.format("concat [%d, %d] of %s", minCount, maxCount,
				syllables);
		String fix = "";
		if (fixStart != null) {
			fix += "starting with " + fixStart;
		}
		if (fixRandom != null) {
			if (fix.length() > 0) {
				fix += ", ";
			}
			fix += "including " + fixRandom;
		}
		if (fixEnd != null) {
			if (fix.length() > 0) {
				fix += ", ";
			}
			fix += "ending with " + fixEnd;
		}
		if (fix.length() > 0) {
			result += fix;
		}
		return result;
	}

	@Override
	public List<DataType> getSupportedDataTypes() {
		return types;
	}

}
